package com.harboursoftware.xstorage.ac;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;

import com.harboursoftware.xstorage.model.XUser;

/**
 * 
 * @author Simon Leung
 * @since 0.1
 */
public class AccessControlPolicy implements Serializable {
    private static final long serialVersionUID = 1L;
    private XUser owner;
    private List<Grant> accessControlList;

    public final static String CANNED_ACP_PRIVATE = "private";
    public final static String CANNED_ACP_PUBLIC_READE = "public-read";
    public final static String CANNED_ACP_PUBLIC_READ_WRITE = "public-read-write";
    public final static String CANNED_ACP_AUTHENTICATED_READ = "authenticated-read";
    public final static String CANNED_ACP_BUCKET_OWNER_READ = "bucket-owner-read";
    public final static String CANNED_ACP_BUCKET_OWNER_FULL_CONTROL = "bucket-owner-full-control";

    public AccessControlPolicy() {

    }

    public AccessControlPolicy(XUser owner, List<Grant> accessControlList) {
        this.owner = owner;
        this.accessControlList = accessControlList;
    }

    public boolean checkPermission(XUser user, Permission permission) {
        if (isOwner(user) && (permission == Permission.READ_ACP || permission == Permission.WRITE_ACP)) {
            // The owner of a bucket or object always has this two permissions
            // implicitly.
            return true;
        }

        if (accessControlList == null) {
            return false;
        }

        for (Grant grant : accessControlList) {
            if (grant.checkPermission(user, permission)) {
                return true;
            }
        }

        return false;
    }

    public boolean isOwner(XUser user) {
        if (user == null || user.getId() == null) {
            return false;
        }

        if (owner == null || owner.getId() == null) {
            return false;
        }

        if (user.getId().equals(owner.getId())) {
            return true;
        }

        return false;
    }

    public XUser getOwner() {
        return owner;
    }

    public void setOwner(XUser owner) {
        this.owner = owner;
    }

    @SuppressWarnings("unchecked")
    public List<Grant> getAccessControlList() {
        return accessControlList == null ? Collections.EMPTY_LIST : accessControlList;
    }

    public void setAccessControlList(List<Grant> accessControlList) {
        this.accessControlList = accessControlList;
    }

    public String toXML() {
        StringBuilder strBuilder = new StringBuilder();
        strBuilder.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
        strBuilder.append("<AccessControlPolicy>");
        strBuilder.append("<Owner>");
        strBuilder.append("<ID>");
        strBuilder.append(owner.getId());
        strBuilder.append("</ID>");
        strBuilder.append("<DisplayName>");
        strBuilder.append(owner.getDisplayName());
        strBuilder.append("</DisplayName>");
        strBuilder.append("</Owner>");
        strBuilder.append("<AccessControlList>");
        for (Grant acl : accessControlList) {
            strBuilder.append(acl.toXML());
        }
        strBuilder.append("</AccessControlList>");
        strBuilder.append("</AccessControlPolicy>");
        return strBuilder.toString();
    }

    /**
     * about more details about xml format,refer to <a
     * href="http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketPUTacl.html">PUT Bucket acl</a>.
     * 
     * @param xml
     * @return
     */
    @SuppressWarnings("unchecked")
    public static AccessControlPolicy fromXML(String xml) {
        if (xml == null) {
            throw new IllegalArgumentException("argument xml can not be null");
        }
        Document document = null;
        try {
            document = DocumentHelper.parseText(xml);
        } catch (DocumentException e) {
            throw new IllegalArgumentException("xml is invalid", e);
        }
        Element rootElement = document.getRootElement();

        Element ownerElement = rootElement.element("Owner");
        String ownerId = ownerElement.elementText("ID");
        String ownerDisplayName = ownerElement.elementText("DisplayName");

        XUser owner = new XUser();
        owner.setId(ownerId);
        owner.setDisplayName(ownerDisplayName);

        Element aclElement = rootElement.element("AccessControlList");
        List<Element> grantElements = aclElement.elements("Grant");
        int listCapacity = grantElements.size();
        List<Grant> accessControlList = new ArrayList<Grant>(listCapacity);
        for (Element grantElement : grantElements) {
            Grant grant = xmlElementToGrant(grantElement);
            accessControlList.add(grant);
        }

        AccessControlPolicy acp = new AccessControlPolicy(owner, accessControlList);
        // TODO : 未完成
        // TODO :xml格式错误的处理
        return acp;
    }

    private static Grant xmlElementToGrant(Element element) {
        Element granteeElement = element.element("Grantee");

        String granteeType = granteeElement.attributeValue("type");

        Grantee grantee = null;
        if ("CanonicalUser".equals(granteeType)) {
            String id = granteeElement.elementText("ID");
            String displayName = granteeElement.elementText("DisplayName");
            grantee = new CanonicalUserGrantee(id, displayName);
        } else if ("Group".equals(granteeType)) {
            String uri = granteeElement.elementText("URI");
            try {
                grantee = GroupGrantee.createInstanceByUri(uri);
            } catch (Exception e) {
                // TODO:异常处理
            }
        } else if ("AmazonCustomerByEmail".equals(granteeType)) {
            // TODO:未完成
        } else {
            // TODO:异常处理
        }

        String permissionString = element.elementText("Permission");
        Permission permission = null;
        permission = Permission.fromString(permissionString);
        
        return new Grant(grantee, permission);
    }

    /**
     * create canned access policy, for more details refer to <a
     * href="http://docs.amazonwebservices.com/AmazonS3/2006-03-01/dev/RESTAccessPolicy.html" >Amazon S3 REST Access
     * Control Policy</a>
     * 
     * @param cannedAccessPolicy
     *            Valid Values: private(default) | public-read | public-read-write | authenticated-read |
     *            bucket-owner-read | bucket-owner-full-control
     * @param owner
     *            the owner of object or bucket
     * @param bucketOwner
     *            the owner of bucket,when this AccessControlPolicy is define for bucket the bucketOwner argument will
     *            be ignored
     * @return the corresponding AccessControlPolicy instance
     * @throws InvalidCannedAccessPolicyException
     *             when argument cannedAccessPolicy is invaild
     */
    public static AccessControlPolicy createCannedAccessPolicy(String cannedAccessPolicy, XUser owner, XUser bucketOwner)
        throws InvalidCannedAccessPolicyException {
        if (cannedAccessPolicy == null) {
            cannedAccessPolicy = CANNED_ACP_PRIVATE; // default
        }

        AccessControlPolicy acp = new AccessControlPolicy(owner, new ArrayList<Grant>());
        grantOwnerFullControl(acp, owner);
        if (cannedAccessPolicy.equals(CANNED_ACP_PRIVATE)) {
            // Owner gets FULL_CONTROL.
        } else if (cannedAccessPolicy.equals(CANNED_ACP_PUBLIC_READE)) {
            // Owner gets FULL_CONTROL and the anonymous principal is granted
            // READ access.
            Grantee anonymous = GroupGrantee.ALL_USERS;
            Grant anonymousReadGrant = new Grant(anonymous, Permission.READ);
            acp.accessControlList.add(anonymousReadGrant);

        } else if (cannedAccessPolicy.equals(CANNED_ACP_PUBLIC_READ_WRITE)) {
            // Owner gets FULL_CONTROL, the anonymous principal is granted READ
            // and WRITE access.
            Grantee anonymous = GroupGrantee.ALL_USERS;
            Grant anonymousReadGrant = new Grant(anonymous, Permission.READ);
            Grant anonymousWriteGrant = new Grant(anonymous, Permission.WRITE);
            acp.accessControlList.add(anonymousReadGrant);
            acp.accessControlList.add(anonymousWriteGrant);

        } else if (cannedAccessPolicy.equals(CANNED_ACP_AUTHENTICATED_READ)) {
            // Owner gets FULL_CONTROL, and any principal authenticated as a
            // registered user is granted READ access.
            Grantee authenticatedUser = GroupGrantee.AUTHENTICATED_USERS;
            Grant authenticatedUserReadGrant = new Grant(authenticatedUser, Permission.READ);
            acp.accessControlList.add(authenticatedUserReadGrant);

        } else if (cannedAccessPolicy.equals(CANNED_ACP_BUCKET_OWNER_READ)) {
            // Object Owner gets FULL_CONTROL, Bucket Owner gets READ
            Grantee bucketOwnerGrantee = new CanonicalUserGrantee(bucketOwner.getId(), bucketOwner.getDisplayName());
            Grant bucketOwnerReadGrant = new Grant(bucketOwnerGrantee, Permission.READ);
            acp.accessControlList.add(bucketOwnerReadGrant);
        } else if (cannedAccessPolicy.equals(CANNED_ACP_BUCKET_OWNER_FULL_CONTROL)) {
            // Object Owner gets FULL_CONTROL, Bucket Owner gets FULL_CONTROL
            Grantee bucketOwnerGrantee = new CanonicalUserGrantee(bucketOwner.getId(), bucketOwner.getDisplayName());
            Grant bucketOwnerFullControlGrant = new Grant(bucketOwnerGrantee, Permission.FULL_CONTROL);
            acp.accessControlList.add(bucketOwnerFullControlGrant);
        } else {
            throw new InvalidCannedAccessPolicyException(cannedAccessPolicy);
        }
        return acp;
    }

    private static void grantOwnerFullControl(AccessControlPolicy acp, XUser owner) {
        CanonicalUserGrantee grantee = new CanonicalUserGrantee(owner.getId(), owner.getDisplayName());
        Grant grant = new Grant(grantee, Permission.FULL_CONTROL);
        acp.accessControlList.add(grant);
    }

    @Override
    public boolean equals(Object obj) {
        return EqualsBuilder.reflectionEquals(this, obj);
    }

    @Override
    public int hashCode() {
        return HashCodeBuilder.reflectionHashCode(this);
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this);
    }

    // TODO : 在保存ACP 的时候应该在重复的授权中删除掉小范围的
    // TODO : 保持的时候，应该把大范围的保持在前面
}
